package com.qd.system.treecl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.cdqidi.dto.CacheKey;
import com.cdqidi.util.RedisTemplateUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * 〈一句话功能简述〉<br>
 *
 * @author sjk
 * @since 1.0.0
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class TreeClService implements TreeCl {

    private static final String PREFIX_PARENT_ID = "clTableName_{0}_parentId_{1}";
    private static final String PREFIX_ID = "clTableName_{0}_Id_{1}";
    private static final String PREFIX = "clTableName_{0}_*";
    private static final Integer MOVE_SIZE = 1000;
    private final TreeClRepository treeClRepository;
    private final RedisTemplateUtil<TreeClModel> redisTemplateUtil;

    @Override
    public void add(TreeClDTO dto) {
        clearCache(dto.getClTableName());
        final List<TreeClModel> clModelList = new ArrayList<>();
        final List<TreeModel> modelList = new ArrayList<>();
        final TreeModel treeModel = treeClRepository.getTree(dto);
        int index = 0;
        if (null != treeModel) {
            modelList.add(treeModel);
            final TreeClModel clModel = buildTreeClModel(treeModel, index);
            clModelList.add(clModel);
            buildCl(dto, treeModel, index + 1, clModelList, modelList);
        }
        if (!clModelList.isEmpty()) {
            clModelList.forEach(treeClModel -> {
                final String name = getName(treeClModel.getPid(), modelList);
                if (StringUtils.hasLength(name)) {
                    treeClModel.setPName(name);
                }
            });
            treeClRepository.saveBatchTreeCl(dto, clModelList);
        }
        clModelList.clear();
        modelList.clear();
    }

    @Override
    public void batchAdd(List<TreeClDTO> list) {
        if (null == list || list.isEmpty()) {
            return;
        }
        TreeClDTO dto = list.get(0);
        clearCache(dto.getClTableName());

        final List<TreeClModel> clModelList = new ArrayList<>();
        final List<TreeModel> modelList = new ArrayList<>();

        list.forEach(treeClDto -> {
            final TreeModel treeModel = treeClRepository.getTree(treeClDto);
            int index = 0;
            if (null != treeModel) {
                modelList.add(treeModel);
                final TreeClModel clModel = buildTreeClModel(treeModel, index);
                clModelList.add(clModel);
                buildCl(treeClDto, treeModel, index + 1, clModelList, modelList);
            }
        });
        if (!clModelList.isEmpty()) {
            clModelList.forEach(treeClModel -> {
                final String name = getName(treeClModel.getPid(), modelList);
                if (StringUtils.hasLength(name)) {
                    treeClModel.setPName(name);
                }
            });
            treeClRepository.saveBatchTreeCl(dto, clModelList);
        }
        clModelList.clear();
        modelList.clear();
    }

    @Override
    public void update(TreeClDTO dto) {
        delete(dto);
        add(dto);
    }

    @Override
    public void batchUpdate(List<TreeClDTO> list) {
        batchDelete(list);
        batchAdd(list);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void move(TreeClDTO dto) {
        final List<TreeClModel> list = treeClRepository.getClTreeListByPid(dto);
        if (!list.isEmpty()) {
            if (list.size() > MOVE_SIZE) {
                syncAll(dto);
            } else {
//                list.forEach(treeClModel -> update(dto.setParam(treeClModel.getId())));
                moveToUpdate(list, dto);
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void batchMove(List<TreeClDTO> list) {
        final List<TreeClModel> tl = new ArrayList<>();
        list.forEach(dto -> {
            final List<TreeClModel> tList = treeClRepository.getClTreeListByPid(dto);
            if (!tList.isEmpty()) {
                tl.addAll(tList);
            }
        });
        final TreeClDTO dto = list.get(0);
        if (list.size() > MOVE_SIZE) {
            syncAll(dto);
        } else {
            moveToUpdate(tl, dto);
            tl.clear();
        }
    }

    private void moveToUpdate(List<TreeClModel> tl, TreeClDTO dto) {
        final List<TreeClDTO> allList = new ArrayList<>();
        for (TreeClModel treeClModel : tl) {
            final TreeClDTO newTreeClDTO = new TreeClDTO(dto.getTableName());
            BeanUtil.copyProperties(dto, newTreeClDTO, true);
            newTreeClDTO.setParam(treeClModel.getId());
            allList.add(newTreeClDTO);
        }
        batchUpdate(allList);
        allList.clear();
    }

    @Override
    public void delete(TreeClDTO dto) {
        clearCache(dto.getClTableName());
        final Set<TreeClModel> list = getTreeClById(dto);
        if (list.isEmpty()) {
            return;
        }
        final String[] ids = list.stream().map(TreeClModel::getCid).toArray(String[]::new);
        treeClRepository.deleteTreeClByIds(dto, ids);
        list.clear();
    }

    @Override
    public void batchDelete(List<TreeClDTO> list) {
        if (null == list || list.isEmpty()) {
            return;
        }
        TreeClDTO dto = list.get(0);
        clearCache(dto.getClTableName());
        List<TreeClModel> clList = new ArrayList<>();
        list.forEach(treeClDto -> {
            final Set<TreeClModel> clTreeList = getTreeClById(treeClDto);
            if (!clTreeList.isEmpty()) {
                clList.addAll(clTreeList);
            }
        });
        if (!clList.isEmpty()) {
            String[] ids = clList.stream().map(TreeClModel::getCid).toArray(String[]::new);
            treeClRepository.deleteTreeClByIds(dto, ids);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncAll(TreeClDTO dto) {
        clearCache(dto.getClTableName());
        treeClRepository.deleteTreeClAll(dto);
        final List<TreeModel> allTree = treeClRepository.getAllTree(dto);
        List<TreeClModel> modelList = new ArrayList<>();
        int index;
        TreeModel t;
        for (TreeModel treeModel : allTree) {
            t = BeanUtil.toBean(treeModel, TreeModel.class);
            index = 0;
            t.setYsId(treeModel.getId());
            modelList.add(buildTreeClModel(allTree, index, t, false));
            buildTreeCl(t, allTree, modelList, index);

        }
        treeClRepository.saveBatchTreeCl(dto, modelList);
    }

    @Override
    public Set<TreeClModel> getTreeClByPid(TreeClDTO dto) {
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_PARENT_ID, dto.getClTableName(), dto.getParam());
        final List<TreeClModel> list = redisTemplateUtil.getList(new CacheKey().setKey(key), () -> treeClRepository.getClTreeListByPid(dto), TreeClModel.class);
        if (null == list || list.isEmpty()) {
            return Collections.emptySet();
        }
        return new HashSet<>(list);
    }

    @Override
    public Set<TreeClModel> getTreeClById(TreeClDTO dto) {
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX_ID, dto.getClTableName(), dto.getParam());
        final List<TreeClModel> list = redisTemplateUtil.getList(new CacheKey().setKey(key), () -> treeClRepository.getClTreeList(dto), TreeClModel.class);
        if (null == list || list.isEmpty()) {
            return Collections.emptySet();
        }
        return new HashSet<>(list);
    }

    private void buildTreeCl(TreeModel model, List<TreeModel> allList, List<TreeClModel> treeClModels, int index) {
        final Optional<TreeModel> optional = allList.stream().filter(treeModel -> {
            if (StringUtils.hasLength(model.getPid())) {
                return model.getPid().equals(treeModel.getId());
            }
            return false;
        }).findAny();
        if (optional.isPresent()) {
            index = index + 1;
            final TreeClModel clModel = buildTreeClModel(allList, index, model, true);
            treeClModels.add(clModel);

            final TreeModel treeModel = optional.get();
            model.setId(treeModel.getId());
            model.setPid(treeModel.getPid());
            buildTreeCl(model, allList, treeClModels, index);
        }
    }

    private TreeClModel buildTreeClModel(List<TreeModel> allList, int index, TreeModel treeModel, boolean isSearchParent) {
        String pName = null;
        if (isSearchParent) {
            pName = getName(treeModel.getPid(), allList);
        }
        if (!StringUtils.hasLength(pName)) {
            pName = treeModel.getName();
        }
        final TreeClModel treeClModel = new TreeClModel();
        treeClModel.setCid(IdWorker.getIdStr());
        treeClModel.setDistance(index);
        treeClModel.setId(treeModel.getYsId());
        treeClModel.setName(treeModel.getName());
        if (index == 0) {
            treeClModel.setPid(treeClModel.getId());
        } else {
            treeClModel.setPid(treeModel.getPid());
        }
        treeClModel.setPName(pName);
        return treeClModel;
    }

    private void buildCl(TreeClDTO dto, TreeModel treeModel, int index, List<TreeClModel> clModelList, List<TreeModel> modelList) {
        if (!StringUtils.hasLength(treeModel.getPid())) {
            return;
        }
        dto.setParam(treeModel.getPid());
        final TreeModel model = treeClRepository.getTree(dto);
        if (null != model) {
            modelList.add(BeanUtil.toBean(model, TreeModel.class));
            model.setId(treeModel.getId());
            model.setName(treeModel.getName());
            final TreeClModel clModel = buildTreeClModel(model, index);
            clModelList.add(clModel.setPid(treeModel.getPid()));
            buildCl(dto, model, index + 1, clModelList, modelList);
        }
    }

    private TreeClModel buildTreeClModel(TreeModel treeModel, int index) {
        final TreeClModel treeClModel = new TreeClModel();
        treeClModel.setCid(IdWorker.getIdStr());
        treeClModel.setDistance(index);
        treeClModel.setId(treeModel.getId());
        treeClModel.setName(treeModel.getName());
        if (index == 0) {
            treeClModel.setPid(treeClModel.getId());
            treeClModel.setPName(treeModel.getName());
        }
        return treeClModel;
    }

    private String getName(String id, List<TreeModel> allList) {
        final Optional<TreeModel> optional = allList.stream().filter(treeModel -> id.equals(treeModel.getId())).findAny();
        return optional.map(TreeModel::getName).orElse(null);
    }

    private void clearCache(String clTableName) {
        final String key = RedisTemplateUtil.getRedisCacheKey(PREFIX, clTableName);
        final StringRedisTemplate stringRedisTemplate = redisTemplateUtil.getStringRedisTemplate();
        final Set<String> keys = stringRedisTemplate.keys(key);
        assert keys != null;
        log.info("delete,keys: " + keys);
        stringRedisTemplate.delete(keys);
    }
}